package serveur;

import interfaces.IInvokedMethods;

import java.lang.reflect.Field;
import java.util.ArrayList;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import objet.IHorloge;
import objet.IObjet1;
import objet.IPoint;
import type.Base64;
import type.IMyDate;

/**
 * Cette classe est utilisée pour appeler les méthodes coté serveur et renvoyer le créer le methodResponse
 * en fonction du résultat de l'appel.
 *  
 * @author Kevin Pozderec, Faycal Abka
 *
 */
public class CallMethod {

	/**
	 * Cette méthode permet de faire l'appel au méthode du serveur avec les objets instanciés lors de la réflexion
	 * 
	 * @param methodName		Le nom de la méthode appelée
	 * @param obj				Liste d'objets passés en paramètre de la méthode
	 * @param primitif			Liste des paramètres primitifs
	 * @param cp				Le pool de classe
	 * @param im				L'interface listant les méthodes qu'on peut 
	 * 							invoquer sur le serveur
	 * 
	 * @throws CannotCompileException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	public static String call(String methodName, ArrayList<CtClass> obj, ArrayList<CtClass> objInternes, ArrayList<Object> primitif,	ClassPool cp, IInvokedMethods im) 
			throws InstantiationException, IllegalAccessException, CannotCompileException {		
	
		/*On différencie les méthodes pour pouvoir utiliser les bons types et on ajoute les interfaces requises pour caster les objets*/
		
		if ("pointMove".equals(methodName)){
			CtClass c = obj.get(0);
			c.addInterface(cp.makeInterface("objet.IPoint"));
			IPoint p = ((IPoint)c.toClass().newInstance());			
			im.pointMove(p, ((Integer)primitif.get(0)).intValue(), ((Integer)primitif.get(1)).intValue());
			String s = (objectToString(p, "void",null));
			return s;
		
		}
		
		if ("useObject1".equals(methodName)){
			
			String result ; 			
			CtClass c = obj.get(0);
			c.addInterface(cp.makeInterface("objet.IObjet1"));			
			IObjet1 p = ((IObjet1)c.toClass().newInstance());			
			result=im.useObject1(p, ((Integer)primitif.get(0)).intValue(), ((Boolean)primitif.get(1)).booleanValue(), ((String)primitif.get(2)), ((Double)primitif.get(3)).doubleValue());
			String s = (objectToString(p, "string" , result));
			return s;
		
		}
		
		if ("pingDate".equals(methodName)){
			
			String result;			
			CtClass c = obj.get(0);
			c.addInterface(cp.makeInterface("objet.IHorloge"));			
			IHorloge p = ((IHorloge)c.toClass().newInstance());	
			result=im.pingDate(p);
			String chaine = (objectToString(p, "string" , result));
			return chaine;
		}
		
		return null;
	}

	/**
	 * Sérialiser la réponse (methodResponse)
	 * 
	 * @param o					L'objet à sérialiser
	 * @param typeRetourMethode	type de retour de la méthode appelée
	 * @return
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */	
	private static String objectToString(Object o, String typeRetourMethode,String valeurRetour) throws IllegalArgumentException, IllegalAccessException{
	
		Class<? extends Object> c  = o.getClass();
		StringBuffer 	sb = new StringBuffer();
		int indice_fin;
		
		Field[] 	f = c.getDeclaredFields();	

		indice_fin = f.length - 1; 
		
		sb.append("<methodResponse>");
		sb.append("<params>");
		
		/*GESTION DU RETOUR*/
		if ( typeRetourMethode.equals("void"))
			sb.append("<param><value>void</value></param>");
		else
			sb.append("<param><value><" + typeRetourMethode + ">"+valeurRetour+"</"+typeRetourMethode+"></value></param>");
			
		/*Nous avons pris comme convention de toujoursm mettre le type et l'oid à la fin du tableau des fields*/
		if ("__type".equals(f[indice_fin].getName())){
			sb.append("<param><value><object oid=\"" + f[indice_fin-1].get(o) + "\" "); //oid
			sb.append("type=\"" + f[indice_fin].get(o) + "\">"); //type
			indice_fin--;
		} else {
			sb.append("<param><value><object oid=\"" + f[indice_fin].get(o) + "\">");
		}
				
		
		sb.append("<fields>");
		
		for(int i = 0; i < indice_fin; i++){
			
	
			/*Cette partie est à mettre à jour lors de l'ajout de type complexe,
			 * ajouter une conditionnel sur le nom du type pour identifier le type ajouté et faire un continue
			 * à la fin du corp du if pour passer à l'itération suivante*/
			
			if ( f[i].getType().getSimpleName().equals("IMyDate") ){				
				
				/*type date*/
				
				sb.append("<field name=\"" + f[i].getName() + "\">");
				sb.append("<value><dateTime.iso8601>");				
				IMyDate d = (IMyDate)f[i].get(o);
				sb.append(d.getYear());
				sb.append(d.getMonth());
				sb.append(d.getDay());
				sb.append("T"+d.getHour()+":"+d.getMinute()+":"+d.getSecond());
				
				sb.append("</dateTime.iso8601></value></field>");		
				
				continue;
			}
			
			if ( f[i].getType().getSimpleName().equals("Base64")){
				
				/*type base64*/
				sb.append("<field name=\""+f[i].getName()+"\">");
				sb.append("<value><base64>");
				Base64 b64x = (Base64)f[i].get(o);
				sb.append(b64x.getChaine64());
				sb.append("</base64></value></field>");
				continue;
			}
			
			if ( f[i].get(o).getClass().isArray()){
				
				/*type tableau :
				 * Pour ce type nous devons déterminer le type du tableau afin de pouvoir manipuler le
				 * bon type de tableau et récupèrer les valeurs */
				sb.append("<field name=\""+f[i].getName()+"\">");
				sb.append("<value><array><data>");
				
				Class <?> type = f[i].get(o).getClass().getComponentType();
				Object value = f[i].get(o);
				if ( type.getName().equals("int") ){
					int [] tab = (int[])value;
					for(int x=0;x<tab.length;x++){
						sb.append("<value>"+tab[x]+"</value>");
					}
				}	
				if ( type.getName().equals("double")){
					double [] tab = (double[])value;
					for(int x=0;x<tab.length;x++){
						sb.append("<value><double>"+tab[x]+"</double></value>");
					}
				}
				if ( type.getName().equals("boolean")){
					boolean [] tab = (boolean[])value;
					for(int x=0;x<tab.length;x++){
						sb.append("<value><boolean>");
						if ( tab[x] )
							sb.append("1");
						else
							sb.append("0");
						sb.append("</boolean></value>");
					}
				}
				if ( type.getName().equals("String")){
					String [] tab = (String[])value;
					for(int x=0;x<tab.length;x++){
						sb.append("<value><string>"+tab[x]+"</string></value>");
					}
				}
				if ( type.getName().equals("type.IMyDate") || type.getName().equals("type.MyDate")
						|| type.getName().equals("IMyDate") || type.getName().equals("MyDate")) {
					IMyDate [] tab = (IMyDate[])value;
					for(int x=0;x<tab.length;x++){
						sb.append("<value><dateTime.iso8601>");						
						IMyDate date = tab[x];
						sb.append(date.getYear());
						sb.append(date.getMonth());
						sb.append(date.getDay());
						sb.append("T"+date.getHour()+":"+date.getMinute()+":"+date.getSecond());
						
						sb.append("</dateTime.iso8601></value>");
					}
				}
				if ( type.getName().equals("type.Base64") || type.getName().equals("Base64")){
					Base64 [] tab = (Base64[]) value;
					for(int x=0;x<tab.length;x++){
						sb.append("<value><base64>"+tab[x].getChaine64()+"</base64></value>");
					}
				}					
				
				sb.append("</data></array></value></field>");
				continue;

			}
			
			/*LowerCase utilisé pour les String qui sont défini en tant que string dans la syntaxe XML RMI*/
			
			sb.append("<field name=\"" + f[i].getName() + "\">");
			sb.append("<value><");
			sb.append(f[i].getType().getSimpleName().toLowerCase());
			sb.append(">");
			sb.append(f[i].get(o));
			sb.append("</");
			sb.append(f[i].getType().getSimpleName().toLowerCase());
			sb.append("></value></field>");			
		}
		sb.append("</fields>");
		sb.append("<methods></methods>");
		sb.append("</object></value></param></params>");
		sb.append("</methodResponse>");
		
		return sb.toString() + "\r\n";
		
	}

}
